/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    Process.h  
  Description: 

*/

#pragma once

namespace SL
{
	namespace M
	{
		delegate void ProcessEntryDelegate(void);
		delegate bool HandleMessageDelegate(m::message* m);

		ref class Game;

		/// <summary>
		/// Represent a text encoding for a file.
		/// </summary>
		public enum class TextEncoding
		{
			/// <summary>
			/// Ascii encoding.
			/// </summary>
			Ascii = m::text::TextEncodingAscii,
			/// <summary>
			/// Utf8 encoding.
			/// </summary>
			Utf8 = m::text::TextEncodingUtf8,
			/// <summary>
			/// Utf16 encoding.
			/// </summary>
			Utf16 = m::text::TextEncodingUtf16
		};

		/// <summary>
		/// Represent a game process.
		/// </summary>
		public ref class Process abstract : Object
		{
		public:
			~Process();
			!Process();

			/// <summary>
			/// Send a message of the given type from this process.
			/// </summary>
			/// <param name="message">The message to send.</param>
			void Send(MessageType^ message);
			/// <summary>
			/// Send a message of the given type from this process.
			/// </summary>
			/// <param name="message">The message to send.</param>
			/// <param name="data">The optional data to specify.</param>
			void Send(MessageType^ message, Object^ data);
			/// <summary>
			/// Send a message of the given type from this process.
			/// </summary>
			/// <param name="message">The message to send.</param>
			/// <param name="data">The optional data to specify.</param>
			/// <param name="cancelLast">True to cancel the last sent message.</param>
			void Send(MessageType^ message, Object^ data, bool cancelLast);
			
			/// <summary>
			/// Represent if the process has been started.
			/// </summary>
			property bool Started
			{
				/// <summary>
				/// Return if the process has been started.
				/// </summary>
				/// <returns>True if the process is started.</returns>
				bool get();
			}
			
			/// <summary>
			/// Represent if the process is resumed.
			/// </summary>
			property bool Resumed
			{
				/// <summary>
				/// Return if the process is running.
				/// </summary>
				/// <returns>True if the process is resumed.</returns>
				bool get();
			}

			/// <summary>
			/// Represent if the process has a message to parse.
			/// </summary>
			property bool HasMessage
			{
				/// <summary>
				/// Return if the process has a message to parse.
				/// </summary>
				/// <returns>True if there is a message to parse.</returns>
				bool get();
			}

			/// <summary>
			/// Represent the game session which this process belongs to.
			/// </summary>
			property Game^ Game
			{
				/// <summary>
				/// Return the game session which this process belongs to.
				/// </summary>
				/// <returns>A game session.</returns>
				SL::M::Game^ get();
			internal:
				void set(SL::M::Game^ game);
			}

			/// <summary>
			/// Represent the currently running process.
			/// </summary>
			/// <remarks>If no process is running in this thread, an exception will be thrown.</remarks>
			static property Process^ Current
			{
				/// <summary>
				/// Represent the currently running process.
				/// </summary>
				/// <returns>A process object.</returns>
				Process^ get();
			}

			/// <summary>
			/// Set or update a timer, using the given timer state object as a tracking. Delay and interval are in seconds, and the last 2 parameters specify the event data to transmit.
			/// </summary>
			/// <param name="delay">The delay before the first tick in seconds.</param>
			/// <param name="interval">The interval between ticks.</param>
			/// <param name="message">The event to send.</param>
			/// <param name="data">The optional data to specify for the event.</param>
			/// <returns>A timer state object which must remain in scope while the process continues to receive ticks.</returns>
			TimerState^ SetPeriodicTimer(
				TimeSpan delay, 
				TimeSpan interval,
				MessageType^ message,
				Object^ data
			);
			/// <summary>
			/// Set or update a timer, using the given timer state object as a tracking. Delay and interval are in seconds, and the last 2 parameters specify the event data to transmit.
			/// </summary>
			/// <param name="delay">The delay before the first tick in seconds.</param>
			/// <param name="interval">The interval between ticks.</param>
			/// <param name="message">The event to send.</param>
			/// <returns>A timer state object which must remain in scope while the process continues to receive ticks.</returns>
			TimerState^ SetPeriodicTimer(
				TimeSpan delay,
				TimeSpan interval,
				MessageType^ message
			);
			/// <summary>
			/// Set or update a timer, using the given timer state object as a tracking. A one shot timer ticks only once.
			/// </summary>
			/// <param name="delay">The delay before the first tick in seconds.</param>
			/// <param name="message">The event to send.</param>
			/// <param name="data">The optional data to specify for the event.</param>/// 
			/// <returns>A timer state object which must remain in scope while the process continues to receive ticks.</returns>
			TimerState^ SetOneShotTimer(
				TimeSpan delay,
				MessageType^ message,
				Object^ data
			);
			/// <summary>
			/// Set or update a timer, using the given timer state object as a tracking. A one shot timer ticks only once.
			/// </summary>
			/// <param name="delay">The delay before the first tick in seconds.</param>
			/// <param name="message">The event to send.</param>
			/// <returns>A timer state object which must remain in scope while the process continues to receive ticks.</returns>
			TimerState^ SetOneShotTimer(
				TimeSpan delay,
				MessageType^ message
			);

			/// <summary>
			/// Read a file from the disk.
			/// </summary>
			/// <param name="fileName">The name of the file to read.</param>
			/// <returns>The file data.</returns>
			static array<byte>^ ReadFile(String^ fileName);
			/// <summary>
			/// Write a file to the disk.
			/// </summary>
			/// <param name="fileName">The name of the file to read.</param>
			/// <returns>The file data.</returns>
			static String^ ReadText(String^ fileName);
			/// <summary>
			/// Write a file to the disk.
			/// </summary>
			/// <param name="fileName">The name of the file to write.</param>
			/// <param name="data">The file data.</param>
			static void WriteFile(String^ fileName, array<byte>^ data);
			/// <summary>
			/// Write a file to the disk.
			/// </summary>
			/// <param name="fileName">The name of the file to write.</param>
			/// <param name="data">The file data.</param>
			/// <param name="encoding">The encoding to save the file as.</param>
			static void WriteText(String^ fileName, String^ data, TextEncoding encoding);
			/// <summary>
			/// Write a file to the disk.
			/// </summary>
			/// <param name="fileName">The name of the file to write.</param>
			/// <param name="package">The file data.</param>
			static void WriteFile(String^ fileName, Package^ package);

			/// <summary>
			/// Sleep until the next frame.
			/// </summary>
			/// <returns>A message object for the wait.</returns>
			/// <remarks>The message type for a frame is <code>us.sl.mcore.frame</code>. Any process can send a frame message and this method will wait on it.</remarks>
			static Message^ Wait();
			/// <summary>
			/// Sleep until any of the given message occurs.
			/// </summary>
			/// <param name="messages">The messages to wait on.</param>
			/// <returns>The first object to be waited on.</returns>
			static Message^ WaitAny(... array<MessageType^>^ messages);
			/// <summary>
			/// Sleep until any of the given message is sent from the given process.
			/// </summary>
			/// <param name="sender">The message sender.</param>
			/// <param name="messages">The messages to wait on.</param>
			/// <returns>The first object to be waited on.</returns>
			static Message^ WaitAnyFromProcess(Process^ sender, ... array<MessageType^>^ messages);
			/// <summary>
			/// Sleep until all of the given messages are sent.
			/// </summary>
			/// <param name="messages">The messages to wait on.</param>
			/// <returns>The messages which were waited on.</returns>
			static ICollection<Message^>^ WaitAll(... array<MessageType^>^ messages);
			/// <summary>
			/// Sleep until all of the given messages are sent.
			/// </summary>
			/// <param name="sender">The message sender.</param>
			/// <param name="messages">The messages to wait on.</param>
			/// <returns>The messages which were waited on.</returns>
			static ICollection<Message^>^ WaitAllFromProcess(Process^ sender, ... array<MessageType^>^ messages);

		protected:
			Process();

			/// <summary>
			/// Wait for the next message result to occur. Do not call this from the managed message handler.
			/// </summary>
			/// <returns>The message which was returned.</returns>
			/// <remarks>This method may return null if the process is stopped.</remarks>
			Message^ WaitForNextMessage();

			/// <summary>
			/// Wait for the next message, optionally putting the process
			/// in a blocking state if it's waiting on either another message or
			/// for the process to be resumed.
			/// </summary>
			/// <returns></returns>
			bool NextMessage();
			/// <summary>
			/// Process entry. A process must implement this and accept messages.
			/// </summary>
			virtual void ProcessEntry() abstract;
			/// <summary>
			/// Entry point for the message handler.
			/// </summary>
			/// <param name="message">The message being received.</param>
			/// <returns>True if the message was handled.</returns>
			/// <remarks>
			/// The message handler should call the base message handler if it doesn't handle the message
			/// it receives, otherwise a deadlock might occur.
			/// </remarks>
			virtual bool HandleMessage(Message^ message);

		internal:
			void ShimProcessEntry();
			bool ShimHandleMessage(m::message* m);

			shim_process* _Process;
			
			ProcessEntryDelegate^ _ProcessEntryDelegate;
			HandleMessageDelegate^ _HandleMessageDelegate;
		private:
			delegate bool NativeMessageHandler(IntPtr message);

			Message^ _nextMessage;
			SL::M::Game^ _game;
			bool _HandlingMessage;
			bool _Waiting;
			System::Runtime::InteropServices::GCHandle^ _peGch;
			System::Runtime::InteropServices::GCHandle^ _hmGch;

			[ThreadStatic]
			static Process^ _CurrentProcess;
		};
	}
}